//Uses.cpp

#include "Uses.h"
#include "PKB.h"

Uses::Uses(){ debug = false; }

Uses::~Uses(){
	//this->uses_list.clear();
}


void Uses::InitializeUses(int num_of_statements) {
	this->debug = true;
	this->num_of_statements = num_of_statements;

}


void Uses::InsertUses(STMT statement, VARINDEX var_index, PROCINDEX proc_index) {

	assert(var_index >= 0 && "var_index must be >= 0");
	assert(statement.statement_number > 0 && "statement number must be > 0");
	int num_statement = statement.statement_number;

	if (debug == false) {
		// check that it is a valid var_index
		assert(PKB::varTable.GetVarName(var_index) != ""); 

		// check that it is a valid proc_index
		assert(PKB::procTable.GetProcName(proc_index) != "");
	}
	
	// 1. Fill var -> stmts association
	if(this->var_stmts.find(var_index) != var_stmts.end()) {
		this->var_stmts[var_index].insert(statement);
	}
	else {
		STMT_ARRAY new_stmts_entry;
		new_stmts_entry.insert(statement);
		this->var_stmts.insert(pair<VARINDEX,STMT_ARRAY>(var_index, new_stmts_entry));
	}

	// 2. Fill stmt->vars association
	auto finder_v = this->stmt_vars.find(statement.statement_number);
	if(finder_v != stmt_vars.end()) {
		finder_v->second.insert(var_index);
	}
	else {
		VARINDEX_ARRAY new_vars_entry;
		new_vars_entry.insert(var_index);
		stmt_vars.insert(pair<unsigned int, VARINDEX_ARRAY>(statement.statement_number, new_vars_entry));
	}

	// 3. Fill var -> procs 
	if (this->var_procs.find(var_index) != var_procs.end()) {
		this->var_procs[var_index].insert(proc_index);
	}
	else {
		PROCINDEX_ARRAY new_procs;
		new_procs.insert(proc_index);
		this->var_procs.insert(pair<VARINDEX, PROCINDEX_ARRAY>(var_index, new_procs));
		
	}

	// 4. Fill proc -> vars
	auto finder_p = this->proc_vars.find(proc_index);
	if (finder_p != proc_vars.end()) {
		finder_p->second.insert(var_index);
	}
	else {
		VARINDEX_ARRAY new_vars;
		new_vars.insert(var_index);
		proc_vars.insert(pair<PROCINDEX, VARINDEX_ARRAY>(proc_index, new_vars));
	}
}


	
VARINDEX_ARRAY Uses::GetVariablesUsedByAssignment(STMT assignment) {
	// ensure  assignment statement is valid
	assert(assignment.statement_number > 0 && assignment.node_type==ASSIGN);
	
	return stmt_vars[assignment.statement_number];
}


STMT_ARRAY Uses::GetAssignmentUsesVariable(VARINDEX var_index) {
	assert(var_index >= 0);
	if (debug == false) {
		assert(PKB::varTable.GetVarName(var_index) != "");
	}
	STMT_ARRAY sub_result_set;
	STMT_ARRAY result_set;
		
	sub_result_set = this->var_stmts[var_index];
	for_each(sub_result_set.begin(), sub_result_set.end(), [&result_set, this](STMT statement) {
		statement.node_type = WHILE;
		if((!debug && (!PKB::parent.IsParent(statement))) || (debug && (!parent_test.IsParent(statement)))) {
			statement.node_type = ASSIGN;
			result_set.insert(statement);
		}
		statement.node_type = IF;
		if((!debug && (!PKB::parent.IsParent(statement))) || (debug && (!parent_test.IsParent(statement)))) {
			statement.node_type = ASSIGN;
			result_set.insert(statement);
		}
	});

	return result_set;
}


BOOLEAN Uses::GetAssignmentUses(STMT assign_statement, VARINDEX var_index) {
	assert(assign_statement.statement_number > 0 && "Must be of ASSIGN type and have statement number > 0");

	// O(log n)
	return (this->stmt_vars[assign_statement.statement_number].find(var_index) != this->stmt_vars[assign_statement.statement_number].end());
}

BOOLEAN Uses::GetContainerUses(STMT statement, VARINDEX var_index, NODETYPE node_type)
{
	/*STMT_ARRAY sub_result;

	sub_result = this->GetContainerUsesVariabl(var_index, node_type);
	// O(n)
	for (auto iter=sub_result.begin(); iter!=sub_result.end(); iter++) {
		if (statement.statement_number == (*iter).statement_number)
			return true;
	}

	return false;*/

	//STMT_ARRAY sub_result;
	VARINDEX_ARRAY sub_result;
	
	
	sub_result = this->GetVariablesUsedByContainer(statement);
	return (sub_result.find(var_index) != sub_result.end());

}


BOOLEAN Uses::GetStatementUses(STMT statement, VARINDEX var_index)
{
	STMT_ARRAY sub_result;
	bool assign_check, if_check, while_check, call_check;
	// O(n)
	assign_check = this->GetAssignmentUses(statement, var_index);
	if_check = this->GetContainerUses(statement, var_index, IF);
	while_check = this->GetContainerUses(statement, var_index, WHILE);

	PROCINDEX called_proc = PKB::calls.GetCalleeByStatement(statement.statement_number);
	if (called_proc > 0) {
		VARINDEX_ARRAY used_vars = this->GetVariablesUsedByProcedure(called_proc);
		call_check = (used_vars.find(var_index) != used_vars.end());
	} else {
		call_check = false;
	}
	
	return (assign_check || if_check || while_check || call_check);

}

BOOLEAN Uses::GetProcedureUses(PROCINDEX proc_index, VARINDEX var_index) 
{
	VARINDEX_ARRAY result_set = this->GetVariablesUsedByProcedure(proc_index);
	return (result_set.find(var_index) != result_set.end());
}



STMT_ARRAY Uses::GetContainerUsesVariable(VARINDEX var_index, NODETYPE node_type) {
	assert(var_index >=0);
	assert(node_type == IF || node_type == WHILE);

	STMT_ARRAY uses_array;
   
	unsigned int i = 0;
	// first obtain all associated statements of var_index
	uses_array = var_stmts[var_index];
	
	STMT temp_test;
	temp_test.node_type = node_type;
	STMT_ARRAY filtered_set, final_set;
	// we want associated statements that are Parents themselves
	for (auto i = uses_array.begin(); i != uses_array.end(); i++) { 
		temp_test.statement_number = i->statement_number;
		if((debug && parent_test.IsParent(temp_test)) ||
			(!debug && PKB::parent.IsParent(temp_test))){
			filtered_set.insert(temp_test);
		} else continue;
	}
	// identify all parents of the associated statements and union both sets
	final_set = IdentifyParents(uses_array, filtered_set, node_type, 0); 
	set_union(final_set.begin(), final_set.end(), filtered_set.begin(), filtered_set.end(), inserter(final_set, final_set.begin()));
	return final_set;
}


STMT_ARRAY Uses::IdentifyParents(STMT_ARRAY children, STMT_ARRAY filtered_set, NODETYPE node_type,  int r_n) {
	unsigned int old_size  = children.size();
	STMT_ARRAY parent_array;
	STMT_ARRAY temp_array;
	if(r_n > 0) {
		temp_array = children;
						
	} 

	for (auto iter_child = children.begin(); iter_child!=children.end(); iter_child++) {
		debug ? 
			parent_array = parent_test.RetrieveTransitiveParent((*iter_child), node_type) // get all parents
		: parent_array = PKB::parent.RetrieveTransitiveParent((*iter_child), node_type); 
		set_union(temp_array.begin(), temp_array.end(), parent_array.begin(), parent_array.end(), inserter(temp_array, temp_array.begin()));

	}
	if (old_size == temp_array.size()) {
	   set_union(temp_array.begin(), temp_array.end(), filtered_set.begin(), filtered_set.end(), inserter(temp_array, temp_array.begin())); 
		return temp_array;
	}
	else 
		return IdentifyParents(temp_array, filtered_set, node_type, r_n+1);

}



VARINDEX_ARRAY Uses::GetVariablesUsedByContainer(STMT container_statement){
/*	assert(((container_statement.node_type == WHILE) || 
		(container_statement.node_type == IF)) && 
		container_statement.statement_number > 0 && "Statement must either be WHILE or IF and must have statement number >0"); 
	// assert that container_statement is a parent
	if (debug == false) {
		assert(PKB::parent.IsParent(container_statement));
	}
	*/
	unsigned int i = 0;
	STMT_ARRAY all_child;

	// Grab all children of container_statement
	debug ? 
		all_child = parent_test.GetChild(container_statement)
	: all_child = PKB::parent.GetChild(container_statement);


	VARINDEX_ARRAY result_set;
	VARINDEX_ARRAY temp_set;
	PROCINDEX proc_index_of_child; // call is child
	NODETYPE current_child_node_type;
	int parent_statement_num = -1;


	// If the statement is a parent, mark its associated variable to be returned
		
	if((debug && parent_test.IsParent(container_statement)) || (!debug && PKB::parent.IsParent(container_statement))) {
		result_set = stmt_vars[container_statement.statement_number];
	}
	// Get all child of container_statement
	for (auto iter = all_child.begin(); iter != all_child.end(); iter++) {

		parent_statement_num = (*iter).statement_number;
		
		current_child_node_type = (*iter).node_type;
		switch(current_child_node_type){
		case ASSIGN: 
			temp_set = GetVariablesUsedByAssignment((*iter));
			break;
		case IF:
		case WHILE:
			temp_set = GetVariablesUsedByContainer((*iter));
			break;			
		case CALL:
			proc_index_of_child = PKB::calls.GetCalleeByStatement((*iter).statement_number);
			temp_set = GetVariablesUsedByProcedure(proc_index_of_child);
			break;
		default: break;
		}
		set_union(result_set.begin(), result_set.end(), temp_set.begin(), temp_set.end(), inserter(result_set,result_set.begin()));
	}
	return result_set;
}


PROCINDEX_ARRAY Uses::GetProcedureUsesVariable(VARINDEX var_index) {
	assert(var_index >= 0);
	
	PROCINDEX_ARRAY temp_set, caller_array, direct_users, proc_array;

	direct_users = var_procs[var_index];

	caller_array = direct_users;
	// for each direct modifier (procedure), we fetch
	// the callers as they are deemed as valid candidates
	// to fulfil the relation

	for (auto p_index = direct_users.begin(); p_index!=direct_users.end(); p_index++) {

		// fetch transitive callers
		temp_set = PKB::calls.RetrieveTransitiveCaller(*p_index);
		set_union(caller_array.begin(), caller_array.end(),
			temp_set.begin(), temp_set.end(),
			inserter(caller_array, caller_array.begin()));
	}

	return caller_array;


}


VARINDEX_ARRAY Uses::GetVariablesUsedByProcedure(PROCINDEX proc_index) {
	assert(proc_index >= 0);
	
	PROCINDEX_ARRAY callee_array;
	VARINDEX_ARRAY modified_vars;
	
	modified_vars = proc_vars[proc_index];

	callee_array = PKB::calls.RetrieveTransitiveCallee(proc_index);

	for (auto p_index=callee_array.begin(); p_index!=callee_array.end(); p_index++) {
		set_union(modified_vars.begin(), modified_vars.end(), 
			proc_vars[*p_index].begin(), proc_vars[*p_index].end(),
			inserter(modified_vars, modified_vars.begin()));
	}

	return modified_vars;


}


/* 
*  Helper functions
*/
STMT_ARRAY Uses::BitsetToStatementArray(boost::dynamic_bitset<> b, NODETYPE node_type) {
	STMT_ARRAY result_set;
	unsigned int i = 0;
	STMT temp_if_test, temp_while_test, temp;
	temp_if_test.node_type = IF;
	temp_while_test.node_type = WHILE;

	for (; i < b.size(); i++) { 

		if(b[i]==1 ) {
			temp.statement_number = i;
			result_set.insert(temp); 
		}
	}
	return result_set;
}

